import { Component } from "@angular/core";
import { keywords } from "./monaco.keywords";
import { NzResizeEvent } from "ng-zorro-antd/resizable";
import { PcodeInterpreter } from "./pcode-interpreter";
import { NzMessageService } from "ng-zorro-antd";
import initCode from "./pcode-init";
import { MipsConverter } from "./converter/mips-converter";
import { PlantumlConverter } from "./converter/plantuml-converter";

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.less"],
})
export class AppComponent {
  title = "pcode-viewer";
  editorOptions = {
    language: "pcode",
    theme: "pcode-white",
    lineNumbers: (x: number) => (x - 1).toString(),
  };
  editor: monaco.editor.ICodeEditor;
  model: monaco.editor.ITextModel;
  constructor(private message: NzMessageService) {}
  // 自动输入
  inputRaw: string = "";
  inputNumber: number[] = [];
  // 自动输入指针
  inputCursor = 0;
  // 输出
  output: string[] = [];
  // 运行限制
  maxRunRestriction = 100000;
  // 初始化代码
  _code: string = initCode.fibonacci;
  get code(): string {
    return this._code;
  }
  set code(value: string) {
    this._code = value;
  }
  // 解释器
  interpreter: PcodeInterpreter;
  // 运行状态
  running = false;
  // 运行
  run() {
    if (this.debugging) {
      // 如果是调试状态那么继续运行
      this.debugDecorations = this.editor.deltaDecorations(
        this.debugDecorations,
        []
      );
    } else {
      // 否则重置到程序开头
      this.reset();
    }
    this.continue();
  }
  reset() {
    // 消除调试标记
    this.debugDecorations = this.editor.deltaDecorations(
      this.debugDecorations,
      []
    );
    // 更新输入区
    this.updateInput();
    // 清空输出区
    this.output = [];
    // 清空调试标记
    this.debugging = false;
    // 运行N步的已完成情况
    this.finishedNumber = 0;
    // 加载已有代码填入解释器
    this.interpreter = new PcodeInterpreter(
      this.code.split("\n"),
      (n) => this.output.push(n),
      () => {
        if (this.inputCursor < this.inputNumber.length) {
          return this.inputNumber[this.inputCursor++];
        }
        return parseInt(prompt("Please enter input:"));
      }
    );
  }
  updateInput() {
    this.inputNumber = this.inputRaw
      .split("\n")
      .filter((x) => x.trim() !== "")
      .map((x) => parseInt(x));
    this.inputCursor = 0;
  }
  continue() {
    // 运行状态
    this.running = true;
    // 每次只运行100行，留出渲染时间
    var cnt = 0;
    while (this.interpreter.count < this.maxRunRestriction) {
      try {
        const result = this.interpreter.next();
        if (!result) {
          this.running = false;
          this.message.info("运行完成");
          break;
        }
        if (++cnt === 100) {
          break;
        }
      } catch (error) {
        this.message.error(
          `运行出错，已运行${this.interpreter.count}步，错误信息:${error.message}`
        );
      }
    }
    if (this.interpreter.count >= this.maxRunRestriction) {
      this.running = false;
      this.message.info("运行超出最大指令数量限制");
    }
    if (this.running) {
      // 还会继续运行
      setTimeout(() => {
        this.continue();
      }, 0);
    }
  }
  // 导入代码
  importedCode = "fibonacci";
  importCode() {
    this.code = initCode[this.importedCode];
  }
  // 运行N步
  runStepNumber = 10;
  finishedNumber = 0;
  runNStep() {
    if (!this.interpreter) {
      this.reset();
    }
    // 运行状态
    this.running = true;
    // 每次只运行100行，留出渲染时间
    while (this.finishedNumber < this.runStepNumber) {
      try {
        const result = this.interpreter.next();
        if (!result) {
          this.running = false;
          this.message.info(`运行已结束，实际运行${this.finishedNumber}`);
          this.finishedNumber = 0;
          break;
        }
        this.finishedNumber++;
        if (this.finishedNumber % 100 === 0) {
          setTimeout(() => {
            this.runNStep();
          }, 0);
          break;
        }
      } catch (error) {
        this.message.error(
          `运行出错，已运行${this.interpreter.count}步，错误信息:${error.message}`
        );
      }
    }
    if (this.finishedNumber === this.runStepNumber) {
      this.running = false;
      this.message.info(`已运行${this.finishedNumber}步`, { nzDuration: 500 });
      this.finishedNumber = 0;
      this.debugging = true;
      this.debugDecorations = this.editor.deltaDecorations(
        this.debugDecorations,
        [
          {
            options: {
              isWholeLine: true,
              className: "step-cursor",
            },
            range: new monaco.Range(
              this.interpreter.cursor + 1,
              1,
              this.interpreter.cursor + 1,
              1
            ),
          },
        ]
      );
    }
  }
  // 调试
  debugging = false;
  debugDecorations: string[] = [];
  // 下一条指令（用于显示）
  nextInstruction: string = "";
  step() {
    // 如果还没有初始化解释器执行复位
    if (!this.interpreter) {
      this.reset();
    }
    if (this.interpreter.nextInstruction()) {
      if (this.debugging) {
        try {
          this.interpreter.next();
        } catch (error) {
          this.message.error(
            `运行出错，已运行${this.interpreter.count}步，错误信息:${error.message}`
          );
        }
      } else {
        this.debugging = true;
        this.message.info("进入单步调试模式");
        this.updateInput();
      }
      this.nextInstruction = this.interpreter.nextInstruction()?.join(" ");
      if (this.nextInstruction) {
        this.nextInstruction +=
          " " + this.calculateComment(this.nextInstruction);

        this.debugDecorations = this.editor.deltaDecorations(
          this.debugDecorations,
          [
            {
              options: {
                isWholeLine: true,
                className: "step-cursor",
              },
              range: new monaco.Range(
                this.interpreter.cursor + 1,
                1,
                this.interpreter.cursor + 1,
                1
              ),
            },
          ]
        );
        this.editor.revealLine(this.interpreter.cursor + 1);
      }
    } else {
      this.message.info("运行到达终点，单步调试结束");
      this.debugging = false;
    }
  }
  // 调整编辑器的大小
  col = 12;
  id = -1;
  onResize({ col }: NzResizeEvent): void {
    cancelAnimationFrame(this.id);
    this.id = requestAnimationFrame(() => {
      this.col = col!;
    });
    this.editor.layout();
  }
  // 获得自动补全源
  getCompletionProvider(component: AppComponent) {
    class IdentCompletionItemProvider
      implements monaco.languages.CompletionItemProvider {
      // 触发字符
      triggerCharacters = [];
      provideCompletionItems(
        model: monaco.editor.ITextModel,
        position: monaco.Position,
        context: monaco.languages.CompletionContext,
        token: monaco.CancellationToken
      ): monaco.languages.ProviderResult<monaco.languages.CompletionList> {
        const result: monaco.languages.CompletionList = {
          incomplete: true,
          suggestions: [],
        };
        const word = model.getWordUntilPosition(position);
        const range: monaco.IRange = {
          startLineNumber: position.lineNumber,
          endLineNumber: position.lineNumber,
          startColumn: 1,
          endColumn: word.endColumn,
        };
        // 建议关键词
        result.suggestions = keywords(range);
        return result;
      }
    }
    return IdentCompletionItemProvider;
  }
  // 根据指令计算其含义
  calculateComment(content: string): string {
    const result = content.match(/(\w{3})\s*(\d+)\s+(\d+)/);
    if (!result) {
      return "";
    }
    const [_, op, p1, p2] = result;
    switch (op.toLowerCase()) {
      case "int":
        switch (p1) {
          case "0":
            return `栈顶分配${p2}个存储单元`;
          default:
            return "";
        }
      case "opr":
        switch (p1) {
          case "0":
            switch (p2) {
              case "0":
                return `结束被调用过程 返回调用点并退栈`;
              case "1":
                return `栈顶原地取相反数`;
              case "2":
                return `推入弹出的次栈顶和栈顶的和`;
              case "3":
                return `推入弹出的次栈顶和栈顶的差值`;
              case "4":
                return `推入弹出的次栈顶和栈顶的乘积`;
              case "5":
                return `推入弹出的次栈顶和栈顶的整除商`;
              case "6":
                return `栈顶原地取二进制最后一位`;
              case "8":
                return `推入弹出的次栈顶和栈顶是(1)否(0)相等`;
              case "9":
                return `推入弹出的次栈顶和栈顶是(1)否(0)不相等`;
              case "10":
                return `推入弹出的次栈顶是(1)否(0)小于栈顶`;
              case "11":
                return `推入弹出的次栈顶是(1)否(0)大于等于栈顶`;
              case "12":
                return `推入弹出的次栈顶是(1)否(0)大于栈顶`;
              case "13":
                return `推入弹出的次栈顶是(1)否(0)小于等于栈顶`;
              case "14":
                return `输出栈顶的数`;
              case "15":
                return `输出换行`;
              case "16":
                return `读输入进栈`;
              default:
                return ``;
            }
          default:
            return ``;
        }
      case "cal":
        return `调用上${p1}层行数为${p2}的过程`;
      case "lit":
        return p1 === "0" ? `将${p2}推入栈中` : ``;
      case "lod":
        return `取上${p1}层中地址为${p2}的值入栈`;
      case "sto":
        return `弹出栈顶存进上${p1}层中地址为${p2}的单元`;
      case "jmp":
        return p1 === "0" ? `转到${p2}行` : ``;
      case "jpc":
        return p1 === "0" ? `若弹出的栈顶为0 转到${p2}行` : ``;
      default:
        return "";
    }
  }
  dirtyLines = [];
  // 避免递归触发修改事件
  clean = false;
  addComments(excludeLine: number) {
    for (const lineNumber of this.dirtyLines) {
      if (
        lineNumber === excludeLine ||
        lineNumber > this.model.getLineCount()
      ) {
        continue;
      }
      // const lineCount = this.model.getLineCount();
      var line = this.model.getLineContent(lineNumber);
      const temp = line.match(/[^a-zA-Z]*([a-zA-Z]+\s*\d+\s*\d+)[^\d]*/);
      if (temp) {
        line = temp[1];
        this.editor.executeEdits("my", [
          {
            range: new monaco.Range(
              lineNumber,
              1,
              lineNumber,
              this.model.getLineMaxColumn(lineNumber)
            ),
            text: line,
            forceMoveMarkers: true,
          },
        ]);
      }
      // 无注释
      if (line.indexOf("#") === -1) {
        const startColumn = this.model.getLineMaxColumn(lineNumber);
        const comment = this.calculateComment(line.trim());
        const op = {
          identifier: { major: 1, minor: 1 },
          range: new monaco.Range(
            lineNumber,
            Math.min(16, startColumn),
            lineNumber,
            startColumn
          ),
          text:
            " ".repeat(startColumn <= 16 ? 16 - startColumn : 0) +
            (comment ? "#* " + comment : ""),
          forceMoveMarkers: true,
        };
        this.editor.executeEdits("my", [op]);
      } else {
        const split = line.split("#");
        for (var i = 2; i < split.length; ++i) {
          split[1] += "#" + split[i];
        }
        const left = split[0].trim();
        const right = split[1].trim();
        if (right.startsWith("*")) {
          const comment = this.calculateComment(left);
          if ("* " + comment !== right) {
            const startColumn = split[0].length + 1;
            const op = {
              identifier: { major: 1, minor: 1 },
              range: new monaco.Range(
                lineNumber,
                startColumn,
                lineNumber,
                startColumn + split[1].length + 1
              ),
              text:
                " ".repeat(startColumn <= 15 ? 15 - startColumn : 0) +
                (comment ? "#* " + comment : ""),
              forceMoveMarkers: true,
            };
            this.editor.executeEdits("my", [op]);
          }
        }
      }
    }
    this.dirtyLines = this.dirtyLines.filter((x) => x === excludeLine);
  }
  onInit(editor) {
    this.editor = editor;
    this.model = this.editor.getModel();
    for (var i = 0; i < this.model.getLineCount(); ++i) {
      this.dirtyLines.push(i + 1);
    }
    this.addComments(-1);
    // 改变编辑器内容事件
    this.model.onDidChangeContent((event) => {
      // console.log(event);
      // console.log(this.clean);
      if (this.clean) {
        return;
      }
      if (this.running) {
        this.running = false;
      }
      if (this.debugging) {
        this.debugging = false;
      }
      if (this.interpreter) {
        this.reset();
        this.interpreter = null;
      }
      for (const change of event.changes) {
        const range = change.range;
        if (
          change.text.trim().startsWith("   #* ") &&
          range.endLineNumber - range.startLineNumber === 0
        ) {
          continue;
        }
        for (var i = range.startLineNumber; i <= range.endLineNumber; ++i) {
          this.dirtyLines.push(i);
        }
        for (var i = 0; i < change.text.split("\n").length - 1; ++i) {
          this.dirtyLines.push(range.startLineNumber + i + 1);
        }
      }
      this.dirtyLines = [...new Set(this.dirtyLines)];
      this.clean = true;
      this.addComments(-1);
      this.clean = false;
    });
    // 注册补全源
    monaco.languages.registerCompletionItemProvider(
      "pcode",
      new (this.getCompletionProvider(this))()
    );
  }
  // 转换成其它语言
  modalVisible = false;
  codeContent = "";
  language = "";
  handleOk() {
    this.modalVisible = false;
  }
  handleCancel() {
    this.modalVisible = false;
  }
  get codeList(): [string, number, number][] {
    return this.code
      .split("\n")
      .filter((x) => x)
      .map((line) => line.split(" "))
      .map(([op, p1, p2]) => [op, parseInt(p1), parseInt(p2)]);
  }
  convertToMIPS() {
    this.language = "mips";
    const converter = new MipsConverter();
    this.codeContent = converter.convert(this.codeList).join("\n");
    this.modalVisible = true;
  }
  convertToPlantUML() {
    this.language = "plantuml";
    const converter = new PlantumlConverter(this.codeList);
    this.codeContent = converter.result.join("\n");
    this.modalVisible = true;
  }
  convertToMIPSBlock() {
    this.language = "mips";
    const converter = new PlantumlConverter(this.codeList);
    this.codeContent = converter.optimizeds_convert().join("\n");
    this.modalVisible = true;
  }
  copyToClipboard(inputElement: HTMLInputElement) {
    inputElement.select();
    document.execCommand("copy");
    inputElement.setSelectionRange(0, 0);
  }
  downloadContent() {
    const suffix = {
      mips: ".s",
      plantuml: ".wsd",
    }[this.language];
    this.download(
      new Date()
        .toJSON()
        .split(/:|-|\./)
        .join("") + suffix,
      this.codeContent
    );
  }
  download(filename: string, text: string) {
    const element = document.createElement("a");
    element.setAttribute(
      "href",
      "data:text/plain;charset=utf-8," + encodeURIComponent(text)
    );
    element.setAttribute("download", filename);
    element.style.display = "none";
    document.body.appendChild(element);
    element.click();
    document.body.removeChild(element);
  }
}
